/**
 * 
 */
package org.swing.utility.net.bundle;

import java.util.ArrayList;
import java.util.List;

import org.swing.utility.net.packet.OSCPacket;

/**
 * @author lqnhu
 *
 */
public class OSCBundle extends OSCPacket {
	/**
	 * This is the initial string of an OSC bundle datagram
	 */
	public static final String TAG = "#bundle";
	/**
	 * The special timetag value to indicate that the bundle be processed as
	 * soon as possible
	 */
	public static final long NOW = 1;
	private static final long SECONDS_FROM_1900_TO_1970 = 2208988800L;
	// private static final byte[] cmd1 = { 0x23, 0x62, 0x75, 0x6E, 0x64, 0x6C,
	// 0x65, 0x00 }; // "#bundle" (4-aligned)
	private long timetag; // 64 bit fixed point seconds since 1 jan 1900
	public final List collPackets = new ArrayList();

	/**
	 * Creates a new empty OSCBundle with timetag set to
	 * &quot;immediately&quot;. SuperCollider recognizes this special timetime
	 * to process the bundle just when it arrives.
	 */
	public OSCBundle() {
		super();
		timetag = NOW; // special code
	}

	/**
	 * Creates a new empty OSCBundle with timetag specified by 'when' which is
	 * milliseconds since 1 jan 1970 as returned by System.currentTimeMillis().
	 * This is converted into an absolute time offset since 1 jan 1900 as
	 * required by the OSC specs.
	 *
	 * @param when
	 *            absolute time tag for the bundle
	 * @see java.lang.System#currentTimeMillis()
	 */
	public OSCBundle(long when) {
		super();
		setTimeTagAbsMillis(when);
	}

	/**
	 * Creates a new empty OSCBundle with timetag specified by 'when' which is
	 * seconds relative to start of session. This relative time offset with
	 * origin of zero is understood by SuperCollider in NonRealTime mode. (see
	 * example in Non-Realtime-Synthesis.rtf).
	 *
	 * @param when
	 *            relative time tag for the bundle
	 */
	public OSCBundle(double when) {
		super();
		setTimeTagRelSecs(when);
	}

	/**
	 * Creates a new empty OSCBundle with timetag specified by a sample frame
	 * offset and an absolute time in milliseconds since 1 jan 1970 as returned
	 * by <code>System.currentTimeMillis()</code>
	 *
	 * @param absMillisOffset
	 *            time offset as returned by
	 *            <code>System.currentTimeMillis</code>
	 * @param sampleFrames
	 *            this offset is added to the milli second offset.
	 * @param sampleRate
	 *            used in conjunction with <code>sampleFrames</code> to
	 *            calculate the time offset.
	 */
	public OSCBundle(long absMillisOffset, long sampleFrames, int sampleRate) {
		super();
		setTimeTagSamples(absMillisOffset, sampleFrames, sampleRate);
	}

	/**
	 * Adds a new <code>OSCPacket</code> to the tail of the bundle. Passing
	 * <code>null</code> is allowed in which case no action is performed.
	 *
	 * @param p
	 *            the packet to add to the tail of the bundle
	 */
	public void addPacket(OSCPacket p) {
		if (p != null) {
			synchronized (collPackets) {
				collPackets.add(p);
			}
		}
	}

	/**
	 * Gets the <code>OSCPacket</code> at the provided index which must be
	 * between 0 inclusive and <code>getPacketCount()</code> exclusive. If
	 * bundles are nested, each nested bundle will count as one packet of
	 * course.
	 *
	 * @param idx
	 *            index of the packet to get
	 * @return packet at index <code>idx</code>
	 */
	public OSCPacket getPacket(int idx) {
		synchronized (collPackets) {
			return (OSCPacket) collPackets.get(idx);
		}
	}

	/**
	 * Returns the number of packets currently assembled in this bundle. If
	 * bundles are nested, each nested bundle will count as one packet of
	 * course.
	 *
	 * @return number of packets assembled in this bundle
	 */
	public int getPacketCount() {
		synchronized (collPackets) {
			return collPackets.size();
		}
	}

	/**
	 * Removes the specified packet
	 *
	 * @param idx
	 *            the index of the packet to remove
	 */
	public void removePacket(int idx) {
		synchronized (collPackets) {
			collPackets.remove(idx);
		}
	}

	// public int getSize()
	// throws IOException
	// {
	// synchronized( collPackets ) {
	// int result = cmd1.length + 8 + (collPackets.size() << 2); // name,
	// timetag, size of each bundle element
	//
	// for( int i = 0; i < collPackets.size(); i++ ) {
	// result += ((OSCPacket) collPackets.get( i )).getSize();
	// }
	//
	// return result;
	// }
	// }
	/**
	 * Sets the bundle's timetag specified by a long which is milliseconds since
	 * 1 jan 1970 as returned by <code>System.currentTimeMillis()</code>. This
	 * is converted into an absolute time offset since 1 jan 1900 as required by
	 * the OSC specs.
	 *
	 * @param when
	 *            absolute time tag for the bundle
	 * @see java.lang.System#currentTimeMillis()
	 */
	public void setTimeTagAbsMillis(long when) {
		final long secsSince1900 = when / 1000 + SECONDS_FROM_1900_TO_1970;
		final long secsFractional = ((when % 1000) << 32) / 1000;
		timetag = (secsSince1900 << 32) | secsFractional;
	}

	public void setTimeTagRaw(long raw) {
		timetag = raw;
	}

	/**
	 * Sets the bundle's timetag specified by a double which is seconds relative
	 * to start of session. This relative time offset with origin of zero is
	 * understood by SuperCollider in NonRealTime mode. (see example in
	 * Non-Realtime-Synthesis.rtf).
	 *
	 * @param when
	 *            relative time tag for the bundle
	 */
	public void setTimeTagRelSecs(double when) {
		// timetag = ((long) when << 32) | (long) ((when % 1.0) * 0x100000000L +
		// 0.5);
		timetag = ((long) when << 32)
				+ (long) ((when % 1.0) * 0x100000000L + 0.5);
	}

	/**
	 * Sets the bundle's timetag as a combination of system absolute time and
	 * sample offset. note that this is not too useful, because supercollider
	 * will execute OSC bundles not with audiorate but controlrate precision!!
	 *
	 * @param absMillisOffset
	 *            time offset as returned by
	 *            <code>System.currentTimeMillis</code>
	 * @param sampleFrames
	 *            this offset is added to the milli second offset.
	 * @param sampleRate
	 *            used in conjunction with <code>sampleFrames</code> to
	 *            calculate the time offset.
	 */
	public void setTimeTagSamples(long absMillisOffset, long sampleFrames,
			int sampleRate) {
		final double seconds = (double) sampleFrames / (double) sampleRate
				+ (double) absMillisOffset / 1000;
		timetag = (((long) seconds + SECONDS_FROM_1900_TO_1970) << 32)
				+ (long) ((seconds % 1.0) * 0x100000000L + 0.5);
	}

	/**
	 * Returns the raw format time tag of the bundle
	 *
	 * @return the bundle's timetag in OSC format
	 *
	 * @todo a utility method to convert this to a more useful value
	 */
	public long getTimeTag() {
		return timetag;
	}
}
